#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
#include <memory>
#include <mutex>

#include "GSLAM/core/Svar.h"
#include "GSLAM/core/Glog.h"
#include "GSLAM/utils/utils_debug.h"
#include "GSLAM/utils/format.h"


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class Log {
public:
    enum LogLevel {
        INFO,
        WARNING,
        ERROR
    };

private:
    std::ostream* os;
    static std::unique_ptr<Log> instance;
    static std::mutex mutex;
    std::ostringstream oss;

    Log(std::ostream* os) : os(os) {}

    static std::string getCurrentTime() {
        time_t now = time(0);
        tm* localtm = localtime(&now);
        char buf[80];
        strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtm);
        return std::string(buf);
    }

    static std::string logLevelToString(LogLevel level) {
        switch (level) {
        case INFO:      return "INFO   ";
        case WARNING:   return "WARNING";
        case ERROR:     return "ERROR  ";
        default:        return "UNKNOWN";
        }
    }

public:
    class LogMessage {

    public:
        LogMessage(Log& log, LogLevel level) : log(log), level(level) {  }

        //LogMessage(const LogMessage&) = delete;
        //LogMessage& operator=(const LogMessage&) = delete;

        template<typename T>
        LogMessage& operator<<(const T& message) {
            messageStream << message;
            return *this;
        }

        ~LogMessage() {
            std::lock_guard<std::mutex> lock(mutex);
            (*log.os) << "[" << getCurrentTime() << "] "
                      << "[" << logLevelToString(level) << "] "
                      << messageStream.str() << std::endl;
        }

    private:
        Log& log;
        LogLevel level;
        std::ostringstream messageStream;
    };

    Log(const Log&) = delete;
    Log& operator=(const Log&) = delete;

    static Log& getInstance() {
        std::lock_guard<std::mutex> lock(mutex);
        if (!instance) {
            instance.reset(new Log(&std::cout));
        }
        return *instance;
    }

    void setOutputStream(std::ostream* newOs) {
        std::lock_guard<std::mutex> lock(mutex);
        os = newOs;
    }

    LogMessage log(LogLevel level) {
        // FIXME: compile error
        //return LogMessage(*this, level);
    }
};

std::unique_ptr<Log> Log::instance;
std::mutex Log::mutex;

#define PLOG(x) Log::getInstance().log(x)

#define L_INFO Log::INFO
#define L_WARN Log::WARNING
#define L_ERR  Log::ERROR


int test_log(int argc, char *argv[])
{
    // Access the global log instance and log a message
    Log& log = Log::getInstance();
    log.log(Log::INFO) << "This is an info message";
    PLOG(L_INFO) << "This is an info message";

    // Log to a file
    std::ofstream logfile("log.txt");
    if (logfile.is_open()) {
        log.setOutputStream(&logfile);
        log.log(Log::ERROR) << "This is an error message";
        logfile.close();
    } else {
        std::cerr << "Unable to open file" << std::endl;
    }

    // Switch back to std::cout
    log.setOutputStream(&std::cout);
    log.log(Log::WARNING) << "This is a warning message";

    return 0;
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

int test_glog(int argc, char *argv[])
{
    GSLAM::SetLogDestination(TRACE, "test");

    LOG(TRACE) << "Trace message (TRACE)";
    LOG(DEBUG) << "Debug message (DEBUG)";
    LOG(INFO) << "Hello world! (INFO)";
    LOG(WARNING) << fmt::sprintf("WARNING message!");
    LOG(ERROR) << fmt::sprintf("ERROR message!");
    //LOG(FATAL) << fmt::sprintf("FATAL message!");

    dbg_pe("ERROR message");
    dbg_pw("WARN message");
    dbg_pi("INFO message");
    dbg_pd("DEBUG message");
    dbg_pt("TRACE message");
    dbg_pt("long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message long message ");

    return 0;
}


int main(int argc, char *argv[])
{
    svar.ParseMain(argc, argv);

    std::string act = svar.GetString("act", "test_glog");

    if( act == "test_log")  test_log(argc, argv);
    if( act == "test_glog") test_glog(argc, argv);

    return -1;
}
